package com.zeyu.framework.tools.cron.service;

import com.google.common.collect.Lists;
import com.zeyu.framework.tools.cron.entity.CronConst;
import com.zeyu.framework.tools.cron.entity.CronCriterias;
import com.zeyu.framework.tools.cron.entity.CronOption;
import com.zeyu.framework.tools.cron.entity.CronResult;
import com.zeyu.framework.utils.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.ParseException;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/**
 * cron表达式处理器
 * Created by zeyuphoenix on 16/9/1.
 */
public class CronService implements CronConst {

    // ================================================================
    // Constants
    // ================================================================

    /**
     * logger
     */
    private static final Logger logger = LoggerFactory.getLogger(CronService.class);

    // ================================================================
    // Fields
    // ================================================================

    // ================================================================
    // Constructors
    // ================================================================

    private CronService() {
    }

    // ================================================================
    // Methods from/for super Interfaces or SuperClass
    // ================================================================

    // ================================================================
    // Public or Protected Methods
    // ================================================================

    /**
     * 根据页面参数构造cron表达式或根据cron表达式获取页面选择项
     *
     * @return 页面需要的表达式结果.
     */
    public CronResult createCronResult(CronCriterias cronCriterias) {

        CronResult cronResult;
        if (cronCriterias == null) {
            logger.error("error web request params.");
            throw new RuntimeException(
                    "error web request params, cronCriterias is null.");
        } else {
            try {
                if (StringUtils.isEmpty(cronCriterias.getCronExpression())) {
                    // 根据页面选择构造cron表达式
                    cronResult = buildCron(cronCriterias.getCronOptions());

                } else {
                    // 根据cron表达式构造页面选择项
                    cronResult = parseCron(cronCriterias.getCronExpression());
                }
            } catch (ParseException e) {
                logger.error("parse error: ", e);
                throw new RuntimeException(e);
            }
        }

        return cronResult;
    }

    // ================================================================
    // Getter & Setter
    // ================================================================

    /**
     * 静态内部类 优点：加载时不会初始化静态变量INSTANCE，因为没有主动使用，达到Lazy
     *
     * @author zeyuphoenix
     */
    private static class SingletonHolder {
        private final static CronService INSTANCE = new CronService();
    }

    /**
     * QuartzService 对象取得
     *
     * @return QuartzService
     */
    public static CronService getInstance() {

        return SingletonHolder.INSTANCE;
    }

    // ================================================================
    // Private Methods
    // ================================================================

    /**
     * <pre>
     * 根据页面选择构造cron表达式
     * 必须包含页面年月日时分秒的选项
     * </pre>
     *
     * @throws ParseException
     */
    private CronResult buildCron(List<CronOption> cronOptions)
            throws ParseException {
        CronResult cronResult = new CronResult();
        cronResult.setCronOptions(cronOptions);
        // 保存转换后的字符串
        String[] array = new String[cronOptions.size()];
        // 依次转换
        for (CronOption cronOption : cronOptions) {
            fillCron(cronOption.getOptionType(),
                    cronOption.getOptionCronType(), cronOption.getValues(),
                    array);
        }
        if (StringUtils.equals(array[CronConst.DAY_OF_WEEK], "?")
                && StringUtils.equals(array[CronConst.DAY_OF_MONTH], "?")) {
            // 天和周不能都不设置
            array[CronConst.DAY_OF_MONTH] = "*";
        }
        if (!StringUtils.equals(array[CronConst.DAY_OF_WEEK], "?")
                && !StringUtils.equals(array[CronConst.DAY_OF_MONTH], "?")) {
            // 天和周不能都设置
            array[CronConst.DAY_OF_WEEK] = "?";
        }

        StringBuilder buffer = new StringBuilder("");
        for (int i = 0; i < array.length; i++) {
            buffer.append(array[i]);
            if (i != array.length - 1) {
                buffer.append(" ");
            }
        }

        // 通过解析器解析，验证正确性，取得标准格式和时间
        CronExpression expression = new CronExpression(buffer.toString());

        // 设置表达式
        cronResult.setCronExpression(expression.toString());

        setExpressionTime(expression, cronResult);

        return cronResult;
    }

    /*
     * 设置任务时间
     */
    private void setExpressionTime(CronExpression expression,
                                   CronResult cronResult) {
        Date date = new Date();
        cronResult.setStartTime(DateFormatUtils.format(date, "yyyy-MM-dd HH:mm:ss"));
        List<String> scheduleTimes = Lists.newArrayList();
        for (int i = 1; i <= 8; i++) {
            date = expression.getNextValidTimeAfter(date);
            scheduleTimes.add(DateFormatUtils.format(date, "yyyy-MM-dd HH:mm:ss"));
            date = new Date(date.getTime() + 1000);
        }

        // 设置计划执行时间
        cronResult.setScheduleTimes(scheduleTimes);
    }

    /**
     * <pre>
     * 根据cron表达式构造页面选择项
     * 必须cron表达式不为空
     * </pre>
     *
     * @throws ParseException
     */
    private CronResult parseCron(String cronExpression) throws ParseException {

        CronResult cronResult = new CronResult();

        // 通过解析器解析，验证正确性，取得标准格式和时间
        CronExpression expression = new CronExpression(cronExpression);

        // 设置表达式
        cronResult.setCronExpression(expression.toString());
        setExpressionTime(expression, cronResult);

        // 页面选择项
        List<CronOption> cronOptions = Lists.newArrayList();
        cronResult.setCronOptions(cronOptions);

        // 生产各个字段的页面选择项
        cronOptions.add(generateCronOption(expression, CronConst.SECOND));
        cronOptions.add(generateCronOption(expression, CronConst.MINUTE));
        cronOptions.add(generateCronOption(expression, CronConst.HOUR));
        cronOptions.add(generateCronOption(expression, CronConst.DAY_OF_MONTH));
        cronOptions.add(generateCronOption(expression, CronConst.MONTH));
        cronOptions.add(generateCronOption(expression, CronConst.DAY_OF_WEEK));
        cronOptions.add(generateCronOption(expression, CronConst.YEAR));

        // 验证天和周
        // 天和周不能都不设置
        int nonCount = 0;
        for (CronOption cronOption : cronOptions) {
            if (cronOption.getOptionType() == CronConst.DAY_OF_MONTH) {
                if (cronOption.getOptionCronType() == CronConst.NON) {
                    nonCount++;
                }
            }
            if (cronOption.getOptionType() == CronConst.DAY_OF_WEEK) {
                if (cronOption.getOptionCronType() == CronConst.NON) {
                    nonCount++;
                }
            }
        }

        int noNonCount = 0;
        // 天和周不能都设置
        for (CronOption cronOption : cronOptions) {
            if (cronOption.getOptionType() == CronConst.DAY_OF_MONTH) {
                if (cronOption.getOptionCronType() != CronConst.NON) {
                    noNonCount++;
                }
            }
            if (cronOption.getOptionType() == CronConst.DAY_OF_WEEK) {
                if (cronOption.getOptionCronType() != CronConst.NON) {
                    noNonCount++;
                }
            }
        }
        // 重新设置天和周
        if (nonCount > 1 || noNonCount > 1) {
            for (CronOption cronOption : cronOptions) {
                if (nonCount > 1) {
                    // 都为?，设置天为*
                    if (cronOption.getOptionType() == CronConst.DAY_OF_MONTH) {
                        cronOption.setOptionCronType(CronConst.EVERY);
                    }
                }
                if (noNonCount > 1) {
                    // 都为*，设置周为？
                    if (cronOption.getOptionType() == CronConst.DAY_OF_WEEK) {
                        cronOption.setOptionCronType(CronConst.NON);
                    }
                }
            }
        }

        return cronResult;
    }

    /**
     * 装填对应值到指定位置
     */
    private void fillCron(int type, int cronType, List<Integer> values,
                          String[] array) {
        if (cronType == CronConst.EVERY) {
            // *格式
            array[type] = "*";
        } else if (cronType == CronConst.RANGE) {
            // -格式
            if (values != null && values.size() >= 2) {
                array[type] = values.get(0) + "-" + values.get(1);
            } else {
                logger.error("error range cron params.");
                array[type] = "*";
            }
        } else if (cronType == CronConst.INCREASE) {
            // /格式
            if (values != null && values.size() >= 2) {
                array[type] = values.get(0) + "/" + values.get(1);
            } else {
                logger.error("error increase cron params.");
                array[type] = "*";
            }

        } else if (cronType == CronConst.DEFINED) {
            // ,格式
            if (values != null && values.size() > 0) {
                int size = values.size();
                StringBuilder buffer = new StringBuilder("");
                for (int i = 0; i < size; i++) {
                    buffer.append(values.get(i));
                    if (i != size - 1) {
                        buffer.append(",");
                    }
                }
                array[type] = buffer.toString();
            } else {
                logger.error("error defined cron params.");
                array[type] = "*";
            }

        } else {
            // ?格式 星期和日期选择其一
            array[type] = "?";
        }
    }

    /**
     * 生成页面选择项
     */
    private CronOption generateCronOption(CronExpression expression, int type) {
        // 获取类型下的字符串
        String summary = expression.getExpressionSetSummary(expression
                .getSet(type));

        // cron选择
        CronOption cronOption = new CronOption();
        cronOption.setOptionType(type);
        // 值列表
        List<Integer> values = Lists.newArrayList();
        cronOption.setValues(values);

        // 具体值
        Set<?> set = expression.getSet(type);

        Iterator<?> itr = set.iterator();
        if (set.contains(CronExpression.ALL_SPEC)) {
            // '*'
            cronOption.setOptionCronType(CronConst.EVERY);
        } else {
            if (set.contains(CronExpression.NO_SPEC_INT)) {
                // '?'
                cronOption.setOptionCronType(CronConst.NON);
            } else if (summary.contains("/")) {
                // '/'
                cronOption.setOptionCronType(CronConst.INCREASE);
                Integer from = (Integer) itr.next();
                Integer to = (Integer) itr.next();
                int interval = to - from;
                values.add(from);
                values.add(interval);
            } else {
                // ','
                cronOption.setOptionCronType(CronConst.DEFINED);
                while (itr.hasNext()) {
                    Integer val = (Integer) itr.next();
                    values.add(val);
                }
            }
        }
        return cronOption;
    }

    // ================================================================
    // Inner or Anonymous Class
    // ================================================================

    // ================================================================
    // Test Methods
    // ================================================================

}
